use crate::core::traits::Dot;
use crate::core::unit::Unit;
use crate::vector::items::Items;
use std::collections::HashMap;
use std::f64::consts::PI;
use std::fmt::{Debug, Formatter};
use std::iter::Sum;
use std::ops::{Add, Div, Mul, Neg, Sub};
use SymbolicExp::*;

#[derive(Clone)]
pub enum SymbolicExp {
    Symbol(String),
    Number(f64),
    Add(Box<SymbolicExp>, Box<SymbolicExp>),
    Sub(Box<SymbolicExp>, Box<SymbolicExp>),
    Mul(Box<SymbolicExp>, Box<SymbolicExp>),
    Div(Box<SymbolicExp>, Box<SymbolicExp>),
    Power(Box<SymbolicExp>, Box<SymbolicExp>),
    Log(Box<SymbolicExp>, Box<SymbolicExp>),
    Ln(Box<SymbolicExp>),
    Sin(Box<SymbolicExp>),
    Sec(Box<SymbolicExp>),
    Cos(Box<SymbolicExp>),
    Csc(Box<SymbolicExp>),
    Tan(Box<SymbolicExp>),
    Cot(Box<SymbolicExp>),
    Arcsin(Box<SymbolicExp>),
    Arccos(Box<SymbolicExp>),
    Arctan(Box<SymbolicExp>),
    Arccot(Box<SymbolicExp>),
}

impl From<f64> for SymbolicExp {
    fn from(value: f64) -> Self {
        Number(value)
    }
}

impl Add for SymbolicExp {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        match (self.clone(), rhs.clone()) {
            (Number(a), Number(b)) => Number(a + b),
            (Number(a), b) if a == 0.0 => b,
            (a, Number(b)) if b == 0.0 => a,
            _ => Self::Add(Box::new(self.clone()), Box::new(rhs.clone())),
        }
    }
}

impl Sub for SymbolicExp {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        match (self.clone(), rhs.clone()) {
            (Number(a), Number(b)) => Number(a - b),
            _ => Self::Sub(Box::new(self.clone()), Box::new(rhs.clone())),
        }
    }
}

impl Mul for SymbolicExp {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        match (self.clone(), rhs.clone()) {
            (Number(a), Number(b)) => Number(a * b),
            (Number(a), b) if a == 1.0 => b,
            (a, Number(b)) if b == 1.0 => a,
            (Number(a), _) if a == 0.0 => Number(0.0),
            (_, Number(b)) if b == 0.0 => Number(0.0),
            _ => Self::Mul(Box::new(self.clone()), Box::new(rhs.clone())),
        }
    }
}

impl Mul<f64> for SymbolicExp {
    type Output = Self;

    fn mul(self, rhs: f64) -> Self::Output {
        match self.clone() {
            Number(a) => Number(a * rhs),
            a if rhs == 1.0 => a,
            _ if rhs == 0.0 => Number(0.0),
            _ => Self::Mul(Box::new(self.clone()), Box::new(Number(rhs))),
        }
    }
}

impl Div for SymbolicExp {
    type Output = Self;

    fn div(self, rhs: Self) -> Self::Output {
        match (self.clone(), rhs.clone()) {
            (Number(a), Number(b)) => Number(a / b),
            _ => Self::Div(Box::new(self.clone()), Box::new(rhs.clone())),
        }
    }
}

impl Default for SymbolicExp {
    fn default() -> Self {
        Number(0.0)
    }
}

impl Unit for SymbolicExp {
    fn unit() -> Self {
        Number(1.0)
    }
}

impl Neg for SymbolicExp {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Number(-1.0) * self
    }
}

impl PartialEq for SymbolicExp {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Number(a), Number(b)) => a == b,
            (Symbol(a), Symbol(b)) => a == b,
            (Add(ref a, ref b), Add(ref c, ref d)) => (a == c && b == d) || (a == d && b == c),
            (Sub(ref a, ref b), Sub(ref c, ref d)) => a == c && b == d,
            (Mul(ref a, ref b), Mul(ref c, ref d)) => (a == c && b == d) || (a == d && b == c),
            (Div(ref a, ref b), Div(ref c, ref d)) => a == c && b == d,
            (Power(ref a, ref b), Power(ref c, ref d)) => a == c && b == d,
            (Log(ref a, ref b), Log(ref c, ref d)) => a == c && b == d,
            (Ln(ref a), Ln(ref b)) => a == b,
            (Sin(ref a), Sin(ref b)) => a == b,
            (Cos(ref a), Cos(ref b)) => a == b,
            (Tan(ref a), Tan(ref b)) => a == b,
            (Cot(ref a), Cot(ref b)) => a == b,
            (Sec(ref a), Sec(ref b)) => a == b,
            (Csc(ref a), Csc(ref b)) => a == b,
            (Arcsin(ref a), Arcsin(ref b)) => a == b,
            (Arccos(ref a), Arccos(ref b)) => a == b,
            (Arctan(ref a), Arctan(ref b)) => a == b,
            (Arccot(ref a), Arccot(ref b)) => a == b,
            (_, _) => false,
        }
    }
}

impl Sum for SymbolicExp {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Number(0.0), |acc, item| acc + item)
    }
}

impl Dot<SymbolicExp> for Items<SymbolicExp> {
    //点乘，向量other在本向量的投影
    fn dot(&self, other: Self) -> SymbolicExp {
        assert_eq!(self.len(), other.len());

        return self
            .0
            .iter()
            .zip(other.0.iter())
            .map(|(x, y)| x.clone() * y.clone())
            .sum();
    }
}

impl SymbolicExp {
    pub(crate) fn log(self, rhs: Self) -> SymbolicExp {
        Log(Box::new(self), Box::new(rhs))
    }
    fn ln(self) -> SymbolicExp {
        Ln(Box::new(self))
    }

    pub(crate) fn sin(self) -> SymbolicExp {
        Sin(Box::new(self))
    }

    fn sec(self) -> SymbolicExp {
        Sec(Box::new(self))
    }

    pub(crate) fn cos(self) -> SymbolicExp {
        Cos(Box::new(self))
    }

    fn csc(self) -> SymbolicExp {
        Csc(Box::new(self))
    }

    pub(crate) fn tan(self) -> SymbolicExp {
        Tan(Box::new(self))
    }

    pub(crate) fn cot(self) -> SymbolicExp {
        Cot(Box::new(self))
    }

    pub(crate) fn arcsin(self) -> SymbolicExp {
        Arcsin(Box::new(self))
    }

    pub(crate) fn arccos(self) -> SymbolicExp {
        Arccos(Box::new(self))
    }

    pub(crate) fn arctan(self) -> SymbolicExp {
        Arctan(Box::new(self))
    }

    pub(crate) fn arccot(self) -> SymbolicExp {
        Arccot(Box::new(self))
    }

    pub(crate) fn power(self, rhs: Self) -> Self {
        match (self.clone(), rhs.clone()) {
            (Number(a), Number(b)) => Number(a.powf(b)),
            _ => Power(Box::new(self), Box::new(rhs)),
        }
    }

    pub(crate) fn change_from_symbol(&self, rhs: Self, variable: &str) -> Self {
        if self.clone() == rhs {
            return Symbol(variable.to_string());
        }

        match self {
            Number(_) => self.clone(),
            Symbol(_) => self.clone(),
            Add(ref a, ref b) => {
                a.change_from_symbol(rhs.clone(), variable) + b.change_from_symbol(rhs, variable)
            }
            Sub(ref a, ref b) => {
                a.change_from_symbol(rhs.clone(), variable) - b.change_from_symbol(rhs, variable)
            }
            Mul(ref a, ref b) => {
                a.change_from_symbol(rhs.clone(), variable) * b.change_from_symbol(rhs, variable)
            }
            Div(ref a, ref b) => {
                a.change_from_symbol(rhs.clone(), variable) / b.change_from_symbol(rhs, variable)
            }
            Power(ref base, ref exponent) => base
                .change_from_symbol(rhs.clone(), variable)
                .power(exponent.clone().change_from_symbol(rhs, variable)),
            Log(ref base, ref exponent) => base
                .change_from_symbol(rhs.clone(), variable)
                .log(exponent.change_from_symbol(rhs, variable)),
            Ln(ref s) => s.change_from_symbol(rhs, variable).ln(),
            Sin(ref s) => s.change_from_symbol(rhs, variable).sin(),
            Cos(ref s) => s.change_from_symbol(rhs, variable).cos(),
            Tan(ref s) => s.change_from_symbol(rhs, variable).tan(),
            Cot(ref s) => s.change_from_symbol(rhs, variable).cot(),
            Sec(ref s) => s.change_from_symbol(rhs, variable).sec(),
            Csc(ref s) => s.change_from_symbol(rhs, variable).csc(),
            Arcsin(ref s) => s.change_from_symbol(rhs, variable).arcsin(),
            Arccos(ref s) => s.change_from_symbol(rhs, variable).arccos(),
            Arctan(ref s) => s.change_from_symbol(rhs, variable).arctan(),
            Arccot(ref s) => s.change_from_symbol(rhs, variable).arccot(),
        }
    }

    pub(crate) fn change_to_symbol(&self, rhs: Self, variable: &str) -> Self {
        match self {
            Number(_) => self.clone(),
            Symbol(ref s) if s == variable => rhs,
            Symbol(_) => self.clone(),
            Add(ref a, ref b) => {
                a.change_to_symbol(rhs.clone(), variable) + b.change_to_symbol(rhs, variable)
            }
            Sub(ref a, ref b) => {
                a.change_to_symbol(rhs.clone(), variable) - b.change_to_symbol(rhs, variable)
            }
            Mul(ref a, ref b) => {
                a.change_to_symbol(rhs.clone(), variable) * b.change_to_symbol(rhs, variable)
            }
            Div(ref a, ref b) => {
                a.change_to_symbol(rhs.clone(), variable) / b.change_to_symbol(rhs, variable)
            }
            Power(ref base, ref exponent) => base
                .change_to_symbol(rhs.clone(), variable)
                .power(exponent.change_to_symbol(rhs, variable)),
            Log(ref base, ref exponent) => base
                .change_to_symbol(rhs.clone(), variable)
                .log(exponent.change_to_symbol(rhs, variable)),
            Ln(ref s) => s.change_to_symbol(rhs, variable).ln(),
            Sin(ref s) => s.change_to_symbol(rhs, variable).sin(),
            Cos(ref s) => s.change_to_symbol(rhs, variable).cos(),
            Tan(ref s) => s.change_to_symbol(rhs, variable).tan(),
            Cot(ref s) => s.change_to_symbol(rhs, variable).cot(),
            Sec(ref s) => s.change_to_symbol(rhs, variable).sec(),
            Csc(ref s) => s.change_to_symbol(rhs, variable).csc(),
            Arcsin(ref s) => s.change_to_symbol(rhs, variable).arcsin(),
            Arccos(ref s) => s.change_to_symbol(rhs, variable).arccos(),
            Arctan(ref s) => s.change_to_symbol(rhs, variable).arctan(),
            Arccot(ref s) => s.change_to_symbol(rhs, variable).arccot(),
        }
    }

    pub(crate) fn derivate(&self, variable: &str) -> SymbolicExp {
        match self {
            Number(_) => Number(0.0),
            Symbol(ref s) if s == variable => Number(1.0),
            Symbol(_) => Number(0.0),
            Add(ref a, ref b) => a.derivate(variable) + b.derivate(variable),
            Sub(ref a, ref b) => a.derivate(variable) - b.derivate(variable),
            Mul(ref a, ref b) => {
                //(uv)'=u'v+uv'
                let da = a.derivate(variable) * (*b.clone());
                let db = b.derivate(variable) * (*a.clone());
                da + db
            }
            Div(ref a, ref b) => {
                //(u/v)'=(u'v-uv')/(v*v)
                let da = a.derivate(variable) * (*b.clone());
                let db = b.derivate(variable) * (*a.clone());
                (da - db) / ((*b.clone()).power(Number(2.0)))
            }
            Power(ref base, ref exponent) => {
                // 应用链式法则和乘积法则
                // d(u^v)/dx = u^v * ( dv/dx * ln(m) + du/dx * (v/u))
                // 对基数求偏导
                let du_dx = base.derivate(variable);
                //对指数求偏导
                let dv_dx = exponent.derivate(variable);
                ((*base.clone()).power(*exponent.clone()))
                    * ((dv_dx * ((*base.clone()).ln()))
                        + (du_dx * (*exponent.clone() / *base.clone())))
            }
            Log(ref base, ref exponent) => {
                return (((*exponent.clone()).ln()) / ((*base.clone()).ln())).derivate(variable);
                // // 对基数求偏导
                // let du_dx = base.derivate(variable);
                // //对指数求偏导
                // let dv_dx = exponent.derivate(variable);
                // let part1 = dv_dx * ((*base.clone()).ln()) / (*exponent.clone());
                // let part2 = (*exponent.clone()).ln() * du_dx / (*base.clone());
                // let part3 = (*base.clone()).ln().power(Number(2.0));
                // return (part1 - part2)/ part3;
            }
            Ln(ref s) => (Number(1.0) / *s.clone()) * s.derivate(variable),
            Sin(ref s) => s.clone().cos() * s.derivate(variable),
            Cos(ref s) => Number(-1.0) * s.clone().sin() * s.derivate(variable),
            Tan(ref s) => s.clone().sec().power(Number(2.0)) * s.derivate(variable),
            Cot(ref s) => {
                Number(-1.0) * (s.clone().csc().power(Number(2.0))) * s.derivate(variable)
            }
            Sec(ref s) => s.clone().sec() * s.clone().tan() * s.derivate(variable),
            Csc(ref s) => Number(-1.0) * s.clone().csc() * s.clone().cot() * s.derivate(variable),
            Arcsin(ref s) => {
                (Number(1.0) - ((*s.clone()).power(Number(2.0)))).power(Number(-0.5))
                    * s.derivate(variable)
            }
            Arccos(ref s) => {
                Number(-1.0)
                    * (Number(1.0) - ((*s.clone()).power(Number(2.0)))).power(Number(-0.5))
                    * s.derivate(variable)
            }
            Arctan(ref s) => {
                (Number(1.0) + ((*s.clone()).power(Number(2.0)))).power(Number(-1.0))
                    * s.derivate(variable)
            }
            Arccot(ref s) => {
                Number(-1.0)
                    * ((Number(1.0) + ((*s.clone()).power(Number(2.0)))).power(Number(-1.0))
                        * s.derivate(variable))
            }
        }
    }

    pub(crate) fn run(&self, args: &HashMap<String, f64>) -> f64 {
        match self {
            Number(ref s) => *s,
            Symbol(ref s) => match args[s] {
                _ => args[s],
            },
            Add(ref a, ref b) => a.run(args) + b.run(args),
            Sub(ref a, ref b) => a.run(args) - b.run(args),
            Mul(ref a, ref b) => a.run(args) * b.run(args),
            Div(ref a, ref b) => a.run(args) / b.run(args),
            Power(ref a, ref b) => a.run(args).powf(b.run(args)),
            Log(ref a, ref b) => a.run(args).log(b.run(args)),
            Ln(ref s) => s.run(args).ln(),
            Sin(ref s) => s.run(args).sin(),
            Cos(ref s) => s.run(args).cos(),
            Tan(ref s) => s.run(args).tan(),
            Cot(ref s) => 1.0 / s.run(args).tan(),
            Sec(ref s) => 1.0 / s.run(args).sin(),
            Csc(ref s) => 1.0 / s.run(args).cos(),
            Arcsin(ref s) => s.run(args).asin(),
            Arccos(ref s) => s.run(args).acos(),
            Arctan(ref s) => s.run(args).atan(),
            Arccot(ref s) => (PI / 2.0) - s.run(args).atan(),
        }
    }
}

impl Debug for SymbolicExp {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Number(ref s) => write!(f, "{:.1}", s)?,
            Symbol(ref s) => write!(f, "{}", s)?,
            Add(ref a, ref b) => write!(f, "({:?}+{:?})", a, b)?,
            Sub(ref a, ref b) => write!(f, "({:?}-{:?})", a, b)?,
            Mul(ref a, ref b) => write!(f, "({:?}*{:?})", a, b)?,
            Div(ref a, ref b) => write!(f, "({:?}/{:?})", a, b)?,
            Power(ref a, ref b) => write!(f, "({:?}^{:?})", a, b)?,
            Log(ref a, ref b) => write!(f, "log({:?},{:?})", a, b)?,
            Ln(ref s) => write!(f, "ln({:?})", s)?,
            Sin(ref s) => write!(f, "sin({:?})", s)?,
            Cos(ref s) => write!(f, "cos({:?})", s)?,
            Tan(ref s) => write!(f, "tan({:?})", s)?,
            Cot(ref s) => write!(f, "cot({:?})", s)?,
            Sec(ref s) => write!(f, "sec({:?})", s)?,
            Csc(ref s) => write!(f, "csc({:?})", s)?,
            Arcsin(ref s) => write!(f, "arcsin({:?})", s)?,
            Arccos(ref s) => write!(f, "arccos({:?})", s)?,
            Arctan(ref s) => write!(f, "arctan({:?})", s)?,
            Arccot(ref s) => write!(f, "arccot({:?})", s)?,
        }
        Ok(())
    }
}
